</> 技術筆記Tech Notes

Ubuntu 深度防禦架構:從核心原理到實戰自動化部署

在現代的 IT 基礎架構中,將一台 Linux 伺服器暴露在網際網路,或是將一台主機放置在物理可接觸的環境中,無異於將其置身於充滿惡意掃描、自動化漏洞利用與實體攻擊的戰場。

單一的安全機制(例如只裝防火牆)早已無法應付複雜的威脅。本篇技術分享將帶領大家探討「深度防禦」在 Ubuntu 系統上的具體實作。我們不僅會提供一套自動化的加固腳本,更會深入作業系統底層,解析這些防禦機制是如何運作的。

核心架構理念:深度防禦的八大防線

這套防禦架構的設計思維建立在「假設攻擊者必然會突破某一道防線」的前提下。因此,我們需要在攻擊鏈的每一個節點設置絆馬索:

  1. 網路邊界:UFW 最小化暴露面,結合 Fail2Ban 阻擋自動化暴力破解。

  2. 通道防護:SSH 四層深度加固與 ForceCommand 指令白/黑名單過濾,搭配 PAM 登入攔截。

  3. 實體存取:監控實體裝置接入並從驅動層阻擋。

  4. 檔案系統:防範核心設定檔被惡意竄改。

  5. 行程執行:揪出潛伏的惡意執行檔與挖礦程式。

  6. 系統服務:防止防護機制自身被惡意終止。

  7. 惡意掃描:定期執行數位指紋與 Rootkit 基準線比對。

  8. 稽核軌跡:確保駭客無法抹除犯罪證據。

以下我們將逐一拆解這些防護模組的底層原理。

模組原理解析

1. 核心金庫與通訊解耦

  • 面臨威脅:寫死在程式碼中的 API Token 極易因為權限設定不當而遭竊。

  • 底層實作

    在 Linux 系統中,檔案建立的瞬間到權限被修改的空窗期,可能存在「瞬讀風險」。我們放棄了常見的 touch + chmod 做法,改用 install -m 600。這能確保檔案在虛擬檔案系統註冊的瞬間,就已經具備絕對嚴格的權限。

    此外,在與 Telegram API 通訊時,若遭遇 DNS 解析失敗或網路延遲,會導致呼叫端被卡死。因此我們的共用發送腳本強制實作了 --connect-timeout--max-time 的非阻塞設計。

2. SSH 深度加固與指令防護

  • 面臨威脅:密碼外洩、私鑰被盜用、以及利用自動化腳本進行破壞性的遠端指令注入。

  • 底層實作

    • 四層加固機制:我們透過 Drop-in 配置,強制廢除密碼登入、強制僅限金鑰登入、並進一步限縮至現代的高強度演算法。最後,透過 AllowUsers 與動態注入 from="CIDR" 參數到 authorized_keys,將連線來源鎖死在特定網段。

    • 指令攔截器:當外部傳入指令時,我們利用 ForceCommand 將指令攔截至 $SSH_ORIGINAL_COMMAND 變數中,並透過正規表示式進行黑名單比對。

    • 工程細節:為了避免破壞 SCP/SFTP 等依賴乾淨標準輸出的二進位通訊協定,攔截器中特別加入了子系統放行邏輯,並確保一般指令透過 /bin/sh -c 執行,而非會載入 Profile 的 /bin/bash -l,徹底解決了常見的協定相容性痛點。

3. 防鎖死設計與網路邊界

  • 面臨威脅:自動化腳本執行防火牆設定時,常因為規則套用順序錯誤,導致維運人員自己被踢出 SSH 連線(俗稱「拔自己網線」)。

  • 底層實作

    在啟用 UFW 防火牆時,腳本實作了「狀態感知」機制。它會透過檢查 $SSH_CONNECTION 判斷當前是否處於遠端 SSH 工作階段;如果是,且 UFW 尚未啟用,腳本會將模式切換為 safe。此時只會將白名單規則寫入核心,但不主動觸發 ufw enable,將最終的啟動權交還給維運人員,確保連線絕對安全。

4. 帳號與關鍵檔案監控

  • 面臨威脅:攻擊者提權後建立後門帳號或修改 Sudo 權限。

  • 底層實作

    這依賴了 Linux 核心的 inotify 子系統。當我們要求監控 /etc/passwd 時,核心會在記憶體的 Inode 結構上掛載一個 Hook。

    工程細節:為什麼我們要同時監聽 close_write, move, create, delete?因為很多編輯器為了確保寫入安全,採用的是「原子替換」。它們會先寫入一個暫存檔,再使用 mv 覆蓋原檔案。如果只監聽 modify,我們將完全漏報這類竄改行為。

5. USB 實體層防護與熱插拔中斷

  • 面臨威脅:BadUSB 鍵盤注入攻擊、惡意儲存裝置掛載。

  • 底層實作

    利用 Linux 核心裝置管理員 udev 監聽來自核心的硬體插拔事件。

    工程細節:在 udev 的 RUN+= 指令中執行的腳本有嚴格的執行時間限制,且會阻擋裝置初始化的流程。因此我們使用 /usr/bin/systemd-run --no-block --collect,將通知任務委託給 Systemd 建立一個拋棄式的背景服務來執行,--collect 參數則確保任務結束後立即釋放記憶體,避免系統遺留垃圾服務。同時配合 USBGuard 從驅動層直接阻擋資料傳輸。

6. 系統狀態守護者

  • 面臨威脅:攻擊者取得 Root 權限後,第一時間嘗試終止我們的防護服務。

  • 底層實作

    我們利用 Systemd 的 Drop-in 機制為現有服務無損注入邏輯。

    工程細節:我們刻意不使用 ExecStopPost=,因為這會覆蓋某些服務原有的停止清理邏輯,造成不可預期的結果。我們採用了更優雅的 OnFailure=service-protector-alert@%n 樣板設計。這意味著只有當服務發生「異常崩潰」或「被強制終止」時才會觸發,這是在日常維運與資安監控之間的合理平衡。

7. 行程白名單與 Procfs 差異比對

  • 面臨威脅:未知惡意程式在記憶體中偷偷運行。

  • 底層實作

    這是一套基於虛擬檔案系統的輪詢稽核機制。腳本透過 ps -e -o exe= 直接讀取每一個執行中行程的可執行檔來源。透過標準的 Linux 文字處理工具 comm -23,快速求出「當前執行快照」與「系統基準線白名單」的差集,揪出不速之客,並實作了基於雜湊的狀態機,實現了優雅的連續警報與自動靜音機制。

8. 核心底層稽核

  • 面臨威脅:攻擊者入侵後執行 history -cecho "" > /var/log/auth.log 銷毀證據。

  • 底層實作

    • Auditd:運作於核心空間。它可以攔截系統呼叫,在應用程式實際讀寫檔案或執行指令「之前」,就透過 Netlink Socket 將操作記錄寫入 /var/log/audit/audit.log。我們特別加入了針對 execve 的系統呼叫追蹤,即使是透過 SSH 進入後產生的任何子行程,都會被完整記錄。

    • Journald:為了防範日誌洪泛攻擊導致硬碟被塞爆,我們強制將 Systemd 的日誌持久化至硬碟,並硬性規範 SystemMaxUse=1G

實戰:系統加固一鍵部署腳本

在理解了底層原理後,我們將上述所有的防禦機制整合成了一份具備「高容錯、防鎖死、冪等性」的 Bash 部署腳本。

安裝步驟:

  1. 取得你的 Telegram Bot Token 與 Chat ID。

  2. 建立腳本 nano ubuntu_ultimate_security_setup.sh 並貼入下方原始碼。

  3. 修改最上方的「全域設定區」。

  4. 賦予權限並執行:chmod +x ubuntu_ultimate_security_setup.sh && sudo ./ubuntu_ultimate_security_setup.sh

#!/bin/bash
set -euo pipefail

# ==============================================================================
# Ubuntu 終極全方位安全監控系統 - 企業加固版一鍵安裝腳本
# 具備:
# - Telegram 通知(Token 隔離、非阻塞)
# - SSH 登入即時通知(PAM)
# - SSH 指令阻擋(ForceCommand + Regex 黑名單,含 scp/sftp/rsync 放行)
# - SSH 四層強化:禁密碼 / 強制 Key(Ed25519) / AllowUsers / authorized_keys from= 限來源
# - UFW(最小暴露面:只放行指定來源到 SSH;預設允許 outgoing → 不影響上網)
# - Fail2Ban(含 TG 封鎖通知)
# - USBGuard + 熱插拔通知
# - 帳號與關鍵檔案監控(inotify)
# - 程序白名單監控
# - 服務崩潰(OnFailure) 與服務檔案守護(inotify)
# - Journald 強化 + Auditd 基礎規則
# - RKHunter 掃描(可設定時間;systemd timer)
#
# 防鎖死保護:
# - SSH_HARDEN_MODE="safe":若 AllowUsers 中有人未放 authorized_keys,暫不關閉密碼登入/不強制 publickey-only
# - 保留救援群組 SSH_RESCUE_GROUP(ForceCommand 不套用於該群組)
# - UFW_ENABLE="safe":若偵測在 SSH 遠端執行且 UFW 尚未啟用,僅寫規則不自動 enable(避免鎖死)
# ==============================================================================


# ==============================================================================
# 【全部可調設定區】(你要求:所有要設定的項目放腳本開頭)
# ==============================================================================

# --- Telegram 通知 ---
MY_TOKEN="xxx"
MY_CHAT_ID="xxx"

# --- 需要守護的 systemd 服務(空白分隔)---
PROTECT_SERVICES="ssh fail2ban usbguard process-monitor tg-account-monitor"

# ------------------------------------------------------------------------------
# SSH 四層強化(Hardening)設定
# ------------------------------------------------------------------------------
SSH_HARDEN_MODE="safe"              # safe / always / never(safe 會避免鎖死)
SSH_PORT="22"

# 第一層:禁用密碼登入(若 safe 且未偵測到 authorized_keys 會暫不套用)
SSH_DISABLE_PASSWORD="true"         # true/false

# 第二層:強制使用 SSH Key(publickey-only)
SSH_ENFORCE_PUBKEY_ONLY="true"      # true/false

# 第二層補強:只接受 Ed25519 使用者公鑰(不想限制就 false)
SSH_REQUIRE_ED25519="true"          # true/false  -> PubkeyAcceptedAlgorithms ssh-ed25519

# 第三層:允許登入的使用者(空白分隔;留空表示不限制)
SSH_ALLOW_USERS="leoshiang"

# 第四層:在 authorized_keys 自動加上 from="CIDR"(留空表示不修改)
SSH_KEY_FROM_CIDR="192.168.1.0/24"

# 其他 SSH 建議參數
SSH_PERMIT_ROOT_LOGIN="no"          # no / prohibit-password / yes(建議 no)
SSH_MAX_AUTH_TRIES="3"
SSH_LOGIN_GRACE_TIME="20"
SSH_CLIENT_ALIVE_INTERVAL="300"
SSH_CLIENT_ALIVE_COUNT_MAX="2"
SSH_DISABLE_FORWARDING="true"       # true/false

# --- 救援機制(強烈建議保留)---
SSH_RESCUE_GROUP="ssh-rescue"       # 此群組不套用 ForceCommand(避免腳本異常鎖死)
SSH_RESCUE_ALLOW_PASSWORD="true"    # true/false(救援群組可保留密碼登入,避免鎖死)

# ------------------------------------------------------------------------------
# SSH 指令防護(ForceCommand)黑名單 Regex(ERE),一行一條規則
# 命中就阻擋;已支援「絕對路徑」繞過:(/path/)?cmd
# ------------------------------------------------------------------------------
SSH_BLOCK_REGEX_LIST=$'(^|[[:space:]])(/[^[:space:]]*/)?(shutdown|reboot|poweroff)([[:space:]]|$)\n'\
$'(^|[[:space:]])(/[^[:space:]]*/)?(useradd|adduser|usermod|userdel|passwd)([[:space:]]|$)\n'\
$'(^|[[:space:]])(/[^[:space:]]*/)?(visudo|sudo)([[:space:]]|$)\n'\
$'(^|[[:space:]])(/[^[:space:]]*/)?(rm|truncate)([[:space:]]+)(-rf[[:space:]]+)?(/var/log|/var/log/|/var/log/.*)([[:space:]]|$)\n'\
$'(^|[[:space:]])(/[^[:space:]]*/)?(systemctl)([[:space:]]+)(stop|disable|mask)([[:space:]]|$)\n'\
$'(^|[[:space:]])(/[^[:space:]]*/)?(sed|perl)([[:space:]]|$).*sshd?_config'
# 更嚴格(可選,取消註解)
# SSH_BLOCK_REGEX_LIST+=$'\n(^|[[:space:]])(/[^[:space:]]*/)?(curl|wget)([[:space:]]|$)'
# SSH_BLOCK_REGEX_LIST+=$'\n(^|[[:space:]])(/[^[:space:]]*/)?(chmod|chown)([[:space:]]|$)'

# ------------------------------------------------------------------------------
# UFW 防火牆(只放行來源網段到 SSH)
# ------------------------------------------------------------------------------
UFW_ENABLE="safe"                   # safe / always / never
UFW_ALLOW_SOURCES="192.168.1.0/24"
UFW_SSH_PORT="22"

# ------------------------------------------------------------------------------
# RKHunter 排程掃描(systemd timer)
# ------------------------------------------------------------------------------
ENABLE_RKHUNTER_SCHEDULE="true"     # true/false
RKHUNTER_RUN_HOUR="22"               # 0-23
RKHUNTER_RUN_MIN="30"                # 0-59


# ==============================================================================
# 【以下為腳本主體】一般不需要改
# ==============================================================================

tg_safe_send() {
  if [ -x /usr/local/bin/tg_send.sh ]; then
    /usr/local/bin/tg_send.sh "$1" >/dev/null 2>&1 || true
  fi
}

is_ssh_session() {
  [ -n "${SSH_CONNECTION:-}" ]
}

ensure_sshd_include_dropin() {
  if ! sudo grep -Eq '^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config; then
    echo "Include /etc/ssh/sshd_config.d/*.conf" | sudo tee -a /etc/ssh/sshd_config >/dev/null
  fi
  sudo mkdir -p /etc/ssh/sshd_config.d
}

authorized_keys_ready_for_user() {
  local u="$1"
  local home
  home="$(getent passwd "$u" | cut -d: -f6 || true)"
  [ -n "$home" ] && sudo test -s "$home/.ssh/authorized_keys"
}

ufw_is_active() {
  sudo ufw status 2>/dev/null | grep -q "Status: active"
}

# ------------------------------------------
# 前置檢查
# ------------------------------------------
if [ -z "${MY_TOKEN:-}" ] || [ "${MY_TOKEN}" = "填入你的機器人Token" ]; then
  echo "[錯誤] 請先在腳本頂端填入您的 Bot Token!"
  exit 1
fi
if [ -z "${MY_CHAT_ID:-}" ] || [ "${MY_CHAT_ID}" = "填入你的數字ID" ]; then
  echo "[錯誤] 請先在腳本頂端填入您的 Chat ID!"
  exit 1
fi

echo "開始安裝「Ubuntu 終極全方位安全監控系統(企業加固版)」..."
export DEBIAN_FRONTEND=noninteractive

# 套件安裝
sudo apt-get update
sudo apt-get install -y \
  inotify-tools curl mailutils rkhunter fail2ban usbguard auditd \
  ufw openssh-server systemd-timesyncd


# ==============================================================================
# 模組 0: 核心憑證安全中心與共用發送器 (Secure Vault)
# ==============================================================================
echo "[0/10] 建立核心憑證金庫與 Telegram 發送器..."

sudo install -o root -g root -m 600 /dev/null /etc/secure-monitoring.env
sudo tee /etc/secure-monitoring.env >/dev/null <<EOF
TG_TOKEN="${MY_TOKEN}"
TG_CHAT_ID="${MY_CHAT_ID}"
EOF

sudo tee /usr/local/bin/tg_send.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
[ -r /etc/secure-monitoring.env ] || { echo "secure-monitoring.env missing" >&2; exit 1; }
# shellcheck disable=SC1091
source /etc/secure-monitoring.env

: "${TG_TOKEN:?TG_TOKEN missing}"
: "${TG_CHAT_ID:?TG_CHAT_ID missing}"

msg="$(printf '%b' "${1:-}")"
curl -sS --fail-with-body --connect-timeout 3 --max-time 6 --retry 2 --retry-delay 1 --retry-all-errors \
  -X POST "[https://api.telegram.org/bot$](https://api.telegram.org/bot$){TG_TOKEN}/sendMessage" \
  --data-urlencode "chat_id=${TG_CHAT_ID}" \
  --data-urlencode "text=${msg}" >/dev/null
EOF
sudo chmod 700 /usr/local/bin/tg_send.sh
sudo chown root:root /usr/local/bin/tg_send.sh


# ==============================================================================
# 模組 1: 帳號與關鍵檔案監控 (Account & File Monitor)
# ==============================================================================
echo "[1/10] 建立帳號與關鍵檔案監控..."

sudo tee /usr/local/bin/tg_account_monitor.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail

TARGETS="/etc/passwd /etc/shadow /etc/sudoers /etc/sudoers.d /usr/local/bin/tg_account_monitor.sh"

VALID_TARGETS=""
for t in $TARGETS; do
  [ -e "$t" ] && VALID_TARGETS="$VALID_TARGETS $t"
done

if [ -z "$VALID_TARGETS" ]; then
  /usr/local/bin/tg_send.sh "⚠️ tg-account-monitor 無有效監控目標,將於 30 秒後重試..." || true
  sleep 30
  exit 0
fi

FILES=""; DIRS=""
for t in $VALID_TARGETS; do
  if [ -d "$t" ]; then
    DIRS="$DIRS $t"
  else
    FILES="$FILES $t"
  fi
done

send_alert() {
  while IFS='|' read -r path action file; do
    MSG="【關鍵檔案變更警報】\n路徑:${path}${file}\n動作:${action}\n時間:$(date '+%Y-%m-%d %H:%M:%S')\n主機:$(hostname)"
    /usr/local/bin/tg_send.sh "$MSG" || true
  done
}

pids=()

if [ -n "$FILES" ]; then
  inotifywait -m --format '%w|%e|%f' -e close_write,attrib,move,create,delete $FILES | send_alert &
  pids+=("$!")
fi

if [ -n "$DIRS" ]; then
  inotifywait -m -r --format '%w|%e|%f' -e close_write,attrib,move,create,delete $DIRS | send_alert &
  pids+=("$!")
fi

cleanup() { kill "${pids[@]:-}" 2>/dev/null || true; }
trap cleanup EXIT INT TERM

wait -n
exit 1
EOF
sudo chmod 700 /usr/local/bin/tg_account_monitor.sh
sudo chown root:root /usr/local/bin/tg_account_monitor.sh

sudo tee /etc/systemd/system/tg-account-monitor.service >/dev/null <<'EOF'
[Unit]
Description=Telegram Account and File Monitor
After=network.target

[Service]
Type=simple
ExecStart=/bin/bash /usr/local/bin/tg_account_monitor.sh
Restart=always
RestartSec=30
User=root

[Install]
WantedBy=multi-user.target
EOF


# ==============================================================================
# 模組 2: 系統 SSH 登入即時通知 (PAM Login Notification)
# ==============================================================================
echo "[2/10] 建立 SSH 遠端登入即時通知..."

sudo tee /usr/local/bin/login_notification.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
if [ "${PAM_TYPE:-}" = "open_session" ]; then
  MSG="【遠端登入警報】\n使用者:${PAM_USER}\n遠端主機:${PAM_RHOST:-本地端}\n終端:${PAM_TTY}\n時間:$(date '+%Y-%m-%d %H:%M:%S')\n主機:$(hostname)"
  /usr/local/bin/tg_send.sh "$MSG" >/dev/null 2>&1 &
fi
exit 0
EOF
sudo chmod 700 /usr/local/bin/login_notification.sh
sudo chown root:root /usr/local/bin/login_notification.sh

if ! grep -q "login_notification.sh" /etc/pam.d/sshd; then
  echo "session optional pam_exec.so /usr/local/bin/login_notification.sh" | sudo tee -a /etc/pam.d/sshd >/dev/null
fi


# ==============================================================================
# 模組 2.5: SSH 指令防護(ForceCommand 過濾/阻擋)
# ==============================================================================
echo "[2.5/10] 加上 SSH 指令防護(過濾/阻擋特定指令)..."

sudo install -o root -g root -m 600 /dev/null /etc/ssh/ssh_command_blocklist.regex
printf "%s\n" "${SSH_BLOCK_REGEX_LIST}" | sudo tee /etc/ssh/ssh_command_blocklist.regex >/dev/null

sudo tee /usr/local/bin/ssh_command_guard.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail

BLOCKLIST="/etc/ssh/ssh_command_blocklist.regex"
ORIG="${SSH_ORIGINAL_COMMAND:-}"
USER="${USER:-${LOGNAME:-unknown}}"

log() { logger -p authpriv.notice -t ssh-cmd-guard -- "$1"; }

tg() {
  if [ -x /usr/local/bin/tg_send.sh ]; then
    /usr/local/bin/tg_send.sh "$1" >/dev/null 2>&1 || true
  fi
}

# 互動 shell(沒有命令)放行
if [ -z "$ORIG" ]; then
  exec /bin/bash -l
fi

# 黑名單匹配(ERE)
if [ -r "$BLOCKLIST" ]; then
  while IFS= read -r rule; do
    [ -z "$rule" ] && continue
    if echo "$ORIG" | grep -Eiq -- "$rule"; then
      SRC="${SSH_CONNECTION:-unknown}"
      MSG="【SSH 指令阻擋】\n使用者:${USER}\n來源:${SRC}\n命令:${ORIG}\n時間:$(date '+%Y-%m-%d %H:%M:%S')\n主機:$(hostname)"
      log "BLOCK user=$USER src=$SRC cmd=$ORIG"
      tg "$MSG"
      echo "Permission denied: command blocked by policy." >&2
      exit 126
    fi
  done < "$BLOCKLIST"
fi

# --- 放行常見檔案傳輸/子系統,避免協定被 shell 輸出破壞 ---
case "$ORIG" in
  internal-sftp*|sftp*|*sftp-server*)
    if [ -x /usr/lib/openssh/sftp-server ]; then
      exec /usr/lib/openssh/sftp-server
    fi
    exec /bin/sh -c "$ORIG"
    ;;
  scp\ -*|scp\ *)
    exec /bin/sh -c "$ORIG"
    ;;
  rsync\ --server*)
    exec /bin/sh -c "$ORIG"
    ;;
esac

log "ALLOW user=$USER cmd=$ORIG"
exec /bin/sh -c "$ORIG"
EOF
sudo chmod 700 /usr/local/bin/ssh_command_guard.sh
sudo chown root:root /usr/local/bin/ssh_command_guard.sh

ensure_sshd_include_dropin
sudo groupadd -f "${SSH_RESCUE_GROUP}"

# ForceCommand:預設套用所有帳號,但排除救援群組避免鎖死
sudo tee /etc/ssh/sshd_config.d/99-command-guard.conf >/dev/null <<EOF
PermitUserEnvironment no
Match Group !${SSH_RESCUE_GROUP}
    ForceCommand /usr/local/bin/ssh_command_guard.sh
Match all
EOF


# ==============================================================================
# 模組 2.6: SSH 四層強化(禁密碼/Key-only/AllowUsers/from=)
# ==============================================================================
echo "[2.6/10] 套用 SSH 四層強化..."

# 1) 準備 host ed25519 key(若缺)
if [ ! -f /etc/ssh/ssh_host_ed25519_key ]; then
  sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" >/dev/null
fi

# 2) safe 模式:檢查 AllowUsers 中的 authorized_keys 是否就緒
keys_ready="true"
if [ -n "${SSH_ALLOW_USERS// }" ]; then
  for u in ${SSH_ALLOW_USERS}; do
    if ! authorized_keys_ready_for_user "$u"; then
      keys_ready="false"
      break
    fi
  done
fi

apply_strict="true"
if [ "${SSH_HARDEN_MODE}" = "never" ]; then
  apply_strict="false"
elif [ "${SSH_HARDEN_MODE}" = "safe" ] && [ "${keys_ready}" != "true" ]; then
  apply_strict="false"
  tg_safe_send "⚠️【SSH safe 模式】AllowUsers 中有人尚未放置 ~/.ssh/authorized_keys(或為空),為避免鎖死:暫不關閉密碼登入/不強制 publickey-only。請先放公鑰後再改 SSH_HARDEN_MODE=always。"
fi

# 3) 寫入 SSH 加固設定(drop-in)
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null <<EOF
# --- Hardening ---
Port ${SSH_PORT}
Protocol 2

HostKey /etc/ssh/ssh_host_ed25519_key

PermitRootLogin ${SSH_PERMIT_ROOT_LOGIN}
MaxAuthTries ${SSH_MAX_AUTH_TRIES}
LoginGraceTime ${SSH_LOGIN_GRACE_TIME}
ClientAliveInterval ${SSH_CLIENT_ALIVE_INTERVAL}
ClientAliveCountMax ${SSH_CLIENT_ALIVE_COUNT_MAX}

PubkeyAuthentication yes
PermitUserEnvironment no
EOF

if [ "${apply_strict}" = "true" ]; then
  if [ "${SSH_DISABLE_PASSWORD}" = "true" ]; then
    cat <<'EOF' | sudo tee -a /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
EOF
  fi

  if [ "${SSH_ENFORCE_PUBKEY_ONLY}" = "true" ]; then
    cat <<'EOF' | sudo tee -a /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null
AuthenticationMethods publickey
EOF
  fi

  if [ "${SSH_REQUIRE_ED25519}" = "true" ]; then
    cat <<'EOF' | sudo tee -a /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null
PubkeyAcceptedAlgorithms ssh-ed25519
EOF
  fi
else
  cat <<'EOF' | sudo tee -a /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null
# safe 模式:暫不強制禁用密碼/強制 publickey-only
EOF
fi

if [ "${SSH_DISABLE_FORWARDING}" = "true" ]; then
  cat <<'EOF' | sudo tee -a /etc/ssh/sshd_config.d/10-hardening.conf >/dev/null
DisableForwarding yes
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
EOF
fi

# 第三層:AllowUsers
if [ -n "${SSH_ALLOW_USERS// }" ]; then
  echo "AllowUsers ${SSH_ALLOW_USERS}" | sudo tee /etc/ssh/sshd_config.d/20-allowusers.conf >/dev/null
else
  sudo rm -f /etc/ssh/sshd_config.d/20-allowusers.conf
fi

# 第四層:authorized_keys from="CIDR"(只對未含 from= 的 key 行補上)
if [ -n "${SSH_KEY_FROM_CIDR// }" ] && [ -n "${SSH_ALLOW_USERS// }" ]; then
  for u in ${SSH_ALLOW_USERS}; do
    home="$(getent passwd "$u" | cut -d: -f6 || true)"
    [ -z "$home" ] && continue
    sudo -u "$u" mkdir -p "$home/.ssh"
    ak="$home/.ssh/authorized_keys"
    if sudo test -s "$ak"; then
      tmp="$(mktemp -p /run ak.${u}.XXXXXX)"
      sudo cat "$ak" | while IFS= read -r line || [ -n "$line" ]; do
        if [[ "$line" =~ ^[[:space:]]*# ]] || [ -z "${line// }" ]; then
          echo "$line" >> "$tmp"
          continue
        fi
        if echo "$line" | grep -Eq '(^|,|[[:space:]])from="'; then
          echo "$line" >> "$tmp"
        else
          echo "from=\"${SSH_KEY_FROM_CIDR}\",$line" >> "$tmp"
        fi
      done
      sudo install -o "$u" -g "$u" -m 600 "$tmp" "$ak"
      rm -f "$tmp"
    else
      tg_safe_send "⚠️【SSH from= 未套用】使用者 ${u} 尚無 ${ak}(或為空)。請先放入公鑰後再重跑或手動在每行 key 前加:from=\"${SSH_KEY_FROM_CIDR}\","
    fi
  done
fi

# 救援群組是否允許密碼登入(可選)
if [ "${SSH_RESCUE_ALLOW_PASSWORD}" = "true" ]; then
  sudo tee /etc/ssh/sshd_config.d/90-rescue.conf >/dev/null <<EOF
Match Group ${SSH_RESCUE_GROUP}
    PasswordAuthentication yes
    KbdInteractiveAuthentication yes
    ChallengeResponseAuthentication no
    PubkeyAuthentication yes
Match all
EOF
else
  sudo rm -f /etc/ssh/sshd_config.d/90-rescue.conf
fi

# 重新載入 ssh
if sudo sshd -t; then
  if systemctl is-active --quiet ssh; then
    sudo systemctl reload ssh
  else
    sudo systemctl restart ssh || true
  fi
fi


# ==============================================================================
# 模組 2.7: UFW 防火牆(最小暴露面)
# ==============================================================================
echo "[2.7/10] 設定 UFW 防火牆..."

if [ "${UFW_ENABLE}" != "never" ]; then
  if [ "${UFW_ENABLE}" = "always" ]; then
    sudo ufw --force reset >/dev/null 2>&1 || true
  fi

  # 不論是否 active,都確保預設策略
  sudo ufw default deny incoming
  sudo ufw default allow outgoing

  # 放行 loopback
  sudo ufw allow in on lo >/dev/null 2>&1 || true
  sudo ufw allow out on lo >/dev/null 2>&1 || true

  # 放行 SSH(限制來源網段)
  if [ -n "${UFW_ALLOW_SOURCES// }" ]; then
    sudo ufw allow from "${UFW_ALLOW_SOURCES}" to any port "${UFW_SSH_PORT}" proto tcp
  else
    sudo ufw allow "${UFW_SSH_PORT}/tcp"
  fi

  # safe:若遠端 SSH 執行且 UFW 尚未 active,避免鎖死 -> 不自動 enable
  if [ "${UFW_ENABLE}" = "safe" ] && is_ssh_session && ! ufw_is_active; then
    tg_safe_send "⚠️【UFW safe】偵測到 SSH 遠端執行,已寫入 UFW 規則但未自動啟用。\n請確認可從 ${UFW_ALLOW_SOURCES} 連線後再手動:sudo ufw enable"
    echo "⚠️ UFW safe:規則已寫入但未自動 enable(避免鎖死)。"
  else
    sudo ufw --force enable >/dev/null 2>&1 || true
  fi
fi


# ==============================================================================
# 模組 3: RKHunter 排程掃描與回報(可設定時間)
# ==============================================================================
echo "[3/10] 設定 RKHunter 掃描與排程..."

sudo rkhunter --update >/dev/null 2>&1 || true
sudo rkhunter --propupd >/dev/null 2>&1 || true

sudo tee /usr/local/bin/rkhunter_scan_tg.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
TMP_FILE="$(mktemp -p /run rkhunter_warn.XXXXXX)"
sudo rkhunter --cronjob --report-warnings-only >"$TMP_FILE" 2>&1 || true

if [ -s "$TMP_FILE" ]; then
  WARNINGS="$(head -n 30 "$TMP_FILE")"
  MSG="【RKHunter 掃描警報】\n主機:$(hostname)\n時間:$(date '+%Y-%m-%d %H:%M:%S')\n\n偵測到異常:\n${WARNINGS}"
  /usr/local/bin/tg_send.sh "$MSG" || true
fi
rm -f "$TMP_FILE"
EOF
sudo chmod 700 /usr/local/bin/rkhunter_scan_tg.sh
sudo chown root:root /usr/local/bin/rkhunter_scan_tg.sh

if [ "${ENABLE_RKHUNTER_SCHEDULE}" = "true" ]; then
  sudo tee /etc/systemd/system/rkhunter-scan.service >/dev/null <<'EOF'
[Unit]
Description=RKHunter scan (TG notify)
After=network.target

[Service]
Type=oneshot
ExecStart=/bin/bash /usr/local/bin/rkhunter_scan_tg.sh
User=root
EOF

  sudo tee /etc/systemd/system/rkhunter-scan.timer >/dev/null <<EOF
[Unit]
Description=Run RKHunter scan daily

[Timer]
OnCalendar=*-*-* ${RKHUNTER_RUN_HOUR}:${RKHUNTER_RUN_MIN}:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

  sudo systemctl daemon-reload
  sudo systemctl enable --now rkhunter-scan.timer
fi


# ==============================================================================
# 模組 4: Fail2Ban 防暴力破解 (Fail2Ban Setup)
# ==============================================================================
echo "[4/10] 設定 Fail2Ban 與 TG 阻擋通知..."

sudo tee /etc/fail2ban/action.d/telegram.conf >/dev/null <<'EOF'
[Definition]
actionban = /usr/local/bin/tg_send.sh "【Fail2Ban 防火牆阻擋】\n惡意來源 IP <ip> 嘗試登入 SSH 失敗次數過多,已被系統封鎖!"
EOF

sudo tee /etc/fail2ban/jail.d/99-tg-sshd.local >/dev/null <<EOF
[sshd]
enabled = true
bantime = 10m
findtime = 10m
maxretry = 5
action = %(action_)s
         telegram
port = ${SSH_PORT}
EOF

sudo systemctl restart fail2ban || sudo fail2ban-client reload || true


# ==============================================================================
# 模組 5: USB 實體防護與異動通知 (USBGuard & Udev)
# ==============================================================================
echo "[5/10] 設定 USBGuard 實體防護與熱插拔通知..."

if [ ! -s /etc/usbguard/rules.conf ]; then
  sudo sh -c 'usbguard generate-policy > /etc/usbguard/rules.conf' 2>/dev/null || true
fi

sudo tee /usr/local/bin/usb_tg_notify.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
ACTION=$1; DEV_ID=$2; MODEL=$3; SERIAL=$4; PRODUCT=$5

if [ "$DEV_ID" = ":" ] && [ -n "$PRODUCT" ]; then
  V_ID=$(printf "%04x" "0x$(echo "$PRODUCT" | cut -d'/' -f1)" 2>/dev/null || true)
  P_ID=$(printf "%04x" "0x$(echo "$PRODUCT" | cut -d'/' -f2)" 2>/dev/null || true)
  if [ -n "$V_ID" ] && [ -n "$P_ID" ]; then DEV_ID="${V_ID}:${P_ID}"; fi
fi
[ "$DEV_ID" = ":" ] || [ -n "$DEV_ID" ] || DEV_ID="未知硬體 ID"
[ -n "$MODEL" ] || MODEL="一般 USB 設備 (拔出時無法讀取)"
[ -n "$SERIAL" ] || SERIAL="無序號"
MODEL=$(echo "$MODEL" | tr '_' ' ')

if [ "$ACTION" = "add" ]; then
  sleep 1.5
  GUARD_INFO=$(sudo usbguard list-devices | grep -i "$DEV_ID" | head -n 1 || true)
  if echo "$GUARD_INFO" | grep -q "block"; then STATE="已被系統阻擋 (Block)"
  elif echo "$GUARD_INFO" | grep -q "allow"; then STATE="允許存取 (Allow)"
  else STATE="狀態未知"; fi
  MSG="【USB 插入警報】\n設備名稱:${MODEL}\n硬體 ID:${DEV_ID}\n設備序號:${SERIAL}\n防護狀態:${STATE}"
elif [ "$ACTION" = "remove" ]; then
  MSG="【USB 拔出通知】\n設備名稱:${MODEL}\n硬體 ID:${DEV_ID}\n設備序號:${SERIAL}"
else
  exit 0
fi

/usr/local/bin/tg_send.sh "$MSG" || true
EOF
sudo chmod 700 /usr/local/bin/usb_tg_notify.sh
sudo chown root:root /usr/local/bin/usb_tg_notify.sh

sudo tee /etc/udev/rules.d/99-usb-tg-notify.rules >/dev/null <<'EOF'
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", RUN+="/usr/bin/systemd-run --no-block --collect /usr/local/bin/usb_tg_notify.sh add '%E{ID_VENDOR_ID}:%E{ID_MODEL_ID}' '%E{ID_MODEL}' '%E{ID_SERIAL_SHORT}' '%E{PRODUCT}'"
ACTION=="remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", RUN+="/usr/bin/systemd-run --no-block --collect /usr/local/bin/usb_tg_notify.sh remove '%E{ID_VENDOR_ID}:%E{ID_MODEL_ID}' '%E{ID_MODEL}' '%E{ID_SERIAL_SHORT}' '%E{PRODUCT}'"
EOF
sudo udevadm control --reload-rules


# ==============================================================================
# 模組 6: 程序白名單監控 (Process Whitelist Monitor)
# ==============================================================================
echo "[6/10] 建立程序白名單與自動循環監控..."

CONF_DIR="/etc/process_monitor"
STATE_DIR="$CONF_DIR/state"
sudo mkdir -p "$STATE_DIR"
WHITELIST_FILE="$CONF_DIR/whitelist.txt"

if [ ! -s "$WHITELIST_FILE" ]; then
  echo "正在掃描系統,建立初始程序白名單..."
  sudo ps -e -o exe= | grep -v '^$' | sort | uniq | sudo tee "$WHITELIST_FILE" >/dev/null
else
  echo "發現既有白名單,已保留原有設定 ($WHITELIST_FILE)"
fi
sudo chmod 700 "$STATE_DIR"
sudo chmod 600 "$WHITELIST_FILE"
sudo chown -R root:root "$CONF_DIR"

sudo tee /usr/local/bin/process_monitor_daemon.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
WHITELIST="/etc/process_monitor/whitelist.txt"
STATE_DIR="/etc/process_monitor/state"

while true; do
  find "$STATE_DIR" -type f -mmin +60 -delete 2>/dev/null || true
  TMP_PROCS="$(mktemp -p /run current_procs.XXXXXX)"
  ps -e -o exe= | grep -v '^$' | sed 's/ (deleted)//g' | sort | uniq > "$TMP_PROCS"
  sort "$WHITELIST" -o "$WHITELIST"

  NEW_PROCS=$(comm -23 "$TMP_PROCS" "$WHITELIST" || true)
  rm -f "$TMP_PROCS"

  if [ -n "$NEW_PROCS" ]; then
    UNREPORTED=""
    now=$(date +%s)

    while IFS= read -r proc; do
      [ -z "$proc" ] && continue
      hash=$(echo -n "$proc" | md5sum | awk '{print $1}')
      state_file="$STATE_DIR/$hash"
      count=0; pause_until=0

      if [ -f "$state_file" ]; then read -r count pause_until < "$state_file" || true; fi
      if [ "$now" -lt "$pause_until" ]; then continue; fi

      count=$((count + 1))
      if [ "$count" -eq 30 ]; then
        UNREPORTED="${UNREPORTED}- ${proc} (第 30/30 次,暫停 3 分鐘)"$'\n'
        pause_until=$((now + 180)); count=0
      else
        UNREPORTED="${UNREPORTED}- ${proc} (第 ${count}/30 次)"$'\n'
      fi
      echo "$count $pause_until" > "$state_file"
    done <<< "$NEW_PROCS"

    if [ -n "$UNREPORTED" ]; then
      MSG="【未授權程序警報】\n主機:$(hostname)\n時間:$(date '+%Y-%m-%d %H:%M:%S')\n\n偵測到未經授權程序:\n${UNREPORTED}"
      /usr/local/bin/tg_send.sh "$MSG" || true
    fi
  fi
  sleep 60
done
EOF
sudo chmod 700 /usr/local/bin/process_monitor_daemon.sh
sudo chown root:root /usr/local/bin/process_monitor_daemon.sh

sudo tee /etc/systemd/system/process-monitor.service >/dev/null <<'EOF'
[Unit]
Description=Process Whitelist Monitor
After=network.target

[Service]
Type=simple
ExecStart=/bin/bash /usr/local/bin/process_monitor_daemon.sh
Restart=always
RestartSec=5
User=root

[Install]
WantedBy=multi-user.target
EOF


# ==============================================================================
# 模組 7: 服務守護者 (Service Protector)
# ==============================================================================
echo "[7/10] 啟動系統服務雙重守護機制..."

sudo tee /usr/local/bin/service_tg_alert.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
EVENT=$1; TARGET=$2

if [ "$EVENT" = "stop" ]; then
  [[ "$TARGET" == *.service ]] || TARGET="${TARGET}.service"
  MSG="【防護服務崩潰警報】\n被觸發服務:${TARGET}\n主機:$(hostname)\n警告:該防禦服務發生異常崩潰 (Failed)!"
elif [ "$EVENT" = "modify" ]; then
  MSG="【服務檔案竄改警報】\n異動檔案:${TARGET}\n主機:$(hostname)"
else
  exit 0
fi

/usr/local/bin/tg_send.sh "$MSG" || true
EOF
sudo chmod 700 /usr/local/bin/service_tg_alert.sh
sudo chown root:root /usr/local/bin/service_tg_alert.sh

sudo tee /etc/systemd/system/service-protector-alert@.service >/dev/null <<'EOF'
[Unit]
Description=Service Failure Alert for %I

[Service]
Type=oneshot
ExecStart=/usr/local/bin/service_tg_alert.sh stop %I
EOF

INOTIFY_TARGETS=""
for srv in $PROTECT_SERVICES; do
  if [ "${srv##*.}" != "service" ]; then srv="${srv}.service"; fi
  if ! systemctl cat "$srv" >/dev/null 2>&1; then
    echo "⚠️ 警告:找不到服務 $srv ,已略過守護。"
    continue
  fi

  DROPIN_DIR="/etc/systemd/system/${srv}.d"
  sudo mkdir -p "$DROPIN_DIR"

  sudo bash -c "cat << EOF > \"$DROPIN_DIR/tg_alert.conf\"
[Unit]
OnFailure=service-protector-alert@%n
EOF"

  SRV_PATH=$(systemctl show -p FragmentPath "$srv" 2>/dev/null | cut -d= -f2 || true)
  if [ -n "$SRV_PATH" ] && [ -e "$SRV_PATH" ]; then
    INOTIFY_TARGETS="$INOTIFY_TARGETS $SRV_PATH"
  fi
  INOTIFY_TARGETS="$INOTIFY_TARGETS $DROPIN_DIR"
done

sudo install -o root -g root -m 600 /dev/null /etc/service-protector.targets
echo "$INOTIFY_TARGETS" | sudo tee /etc/service-protector.targets >/dev/null

sudo tee /usr/local/bin/service_file_monitor.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
TARGETS="$(cat /etc/service-protector.targets 2>/dev/null || true)"

VALID_TARGETS=""
for t in $TARGETS; do
  [ -e "$t" ] && VALID_TARGETS="$VALID_TARGETS $t"
done

if [ -z "$VALID_TARGETS" ]; then
  /usr/local/bin/tg_send.sh "⚠️ service-protector 無有效監控目標,將於 30 秒後重試..." || true
  sleep 30
  exit 0
fi

inotifywait -m --format '%w|%e|%f' -e close_write,attrib,move,create,delete $VALID_TARGETS | \
while IFS='|' read -r path action file; do
  full="${path}${file}"
  if [[ "$full" == /etc/systemd/system/*.service.d/tg_alert.conf ]]; then
    continue
  fi
  /usr/local/bin/service_tg_alert.sh modify "$full" || true
done
EOF
sudo chmod 700 /usr/local/bin/service_file_monitor.sh
sudo chown root:root /usr/local/bin/service_file_monitor.sh

sudo tee /etc/systemd/system/service-protector.service >/dev/null <<'EOF'
[Unit]
Description=Service File Protector
After=network.target

[Service]
Type=simple
ExecStart=/bin/bash /usr/local/bin/service_file_monitor.sh
Restart=always
RestartSec=30
User=root

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload


# ==============================================================================
# 模組 8: 系統日誌強化與底層稽核 (Logging & Auditd)
# ==============================================================================
echo "[8/10] 強化 Journald 與安裝 Auditd 規則..."

sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/99-security.conf >/dev/null <<'EOF'
[Journal]
Storage=persistent
SystemMaxUse=1G
EOF
sudo systemctl restart systemd-journald

sudo tee /etc/audit/rules.d/50-security-baseline.rules >/dev/null <<'EOF'
-w /etc/passwd -p wa -k identity_changes
-w /etc/shadow -p wa -k identity_changes
-w /etc/sudoers -p wa -k identity_changes
-w /etc/sudoers.d/ -p wa -k identity_changes

-w /var/log/audit/ -p wa -k audit_log_tampering

# 記錄命令執行(execve),便於追查(系統忙碌時可能產生大量紀錄)
-a always,exit -F arch=b64 -S execve -F euid=0 -k exec_root
-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=4294967295 -k exec_user
-a always,exit -F arch=b32 -S execve -F euid=0 -k exec_root
-a always,exit -F arch=b32 -S execve -F auid>=1000 -F auid!=4294967295 -k exec_user
EOF

if [ -f /var/log/auth.log ]; then
  echo "-w /var/log/auth.log -p wa -k auth_log_tampering" | sudo tee -a /etc/audit/rules.d/50-security-baseline.rules >/dev/null
fi

sudo systemctl enable --now auditd
sudo augenrules --load >/dev/null 2>&1 || true


# ==============================================================================
# 最終步驟: 啟動所有背景服務 + 上線通知
# ==============================================================================
echo "[10/10] 啟動所有防護服務..."

sudo systemctl daemon-reload

sudo systemctl enable --now tg-account-monitor.service
sudo systemctl enable --now fail2ban
sudo systemctl enable --now usbguard
sudo systemctl enable --now service-protector.service
sudo systemctl enable --now process-monitor.service

FINAL_MSG="【安全系統全面上線】\n主機:$(hostname)\n時間:$(date '+%Y-%m-%d %H:%M:%S')\n-------------------------\n已啟用:\n- Token 金庫隔離 + TG 集中發送\n- 帳號/關鍵檔案監控\n- SSH 登入通知\n- SSH 指令阻擋 (ForceCommand)\n- SSH 四層強化(safe=${SSH_HARDEN_MODE})\n- UFW(${UFW_ENABLE};來源 ${UFW_ALLOW_SOURCES} → SSH:${UFW_SSH_PORT})\n- RKHunter(每日 ${RKHUNTER_RUN_HOUR}:${RKHUNTER_RUN_MIN})\n- Fail2Ban\n- USBGuard\n- Service Protector\n- Process Monitor\n- Journald + Auditd"

tg_safe_send "$FINAL_MSG"

echo "================================================================"
echo "系統全方位防護安裝完畢!您的手機應已收到上線通知。"
echo ""
echo "若需 SSH 防護例外(ForceCommand 不套用),請將使用者加入 ${SSH_RESCUE_GROUP} 群組:"
echo "  sudo usermod -aG ${SSH_RESCUE_GROUP} <username>"
echo ""
if [ "${UFW_ENABLE}" = "safe" ] && is_ssh_session && ! ufw_is_active; then
  echo "注意:偵測到 SSH 遠端執行,UFW 規則已寫入但尚未啟用。"
  echo "確認可從 ${UFW_ALLOW_SOURCES} 連線後再手動:"
  echo "  sudo ufw enable"
fi
echo "================================================================"

日常維運與事件回應

系統上線後,我們進入了正式的維運階段。這套系統採用了許多嚴格的「預設拒絕」原則,因此在日常維運時,你可能會需要處理以下幾種常見的事件回應。

1. SSH 鎖死防護與救援群組管理

  • 情境:你需要執行高權限或會被 ForceCommand 黑名單阻擋的系統維護指令(例如重開機 reboot),或是你需要使用密碼登入緊急處理狀況。

  • 回應方式

    系統預設保留了一個 ssh-rescue 群組。此群組內的帳號不受 ForceCommand 限制,且在 SSH_RESCUE_ALLOW_PASSWORD="true"允許密碼登入

    請將你需要作為「緊急逃生門」的管理員帳號加入該群組:

    sudo usermod -aG ssh-rescue <你的管理員帳號>
    

    加入後,重新登入 SSH 即可獲得完整權限。

2. UFW 安全模式啟用

  • 情境:在部署腳本時,若系統偵測到你是透過遠端 SSH 執行,且 UFW 尚未啟動,為了避免錯誤的設定導致你的連線中斷,UFW 將進入 safe 模式(僅寫入規則不啟動)。

  • 回應方式

    請先確認您當前的來源 IP 確實包含在允許的網段內。確認無誤後,請手動啟用防火牆:

    sudo ufw enable
    

3. 授權合法的新服務

  • 情境:你剛透過 apt installdocker run 部署了一套新的應用服務,隨後立刻收到了 Telegram 的「未授權行程執行警報」。

  • 回應方式

    這是預期內的行為。行程白名單在部署當下即建立了基準線。只要你確認警報中的絕對路徑是合法的新增程式,請將其路徑附加至白名單檔案中:

    echo "/usr/bin/dockerd" | sudo tee -a /etc/process_monitor/whitelist.txt
    

    系統會在其後的輪詢週期中自動認可該程式,停止警報。

4. 實體裝置放行管理

  • 情境:你需要將一個新的外接硬碟插入伺服器進行資料備份,但插入後收到「已被系統阻擋」的警報,且系統無法掛載該磁碟。

  • 回應方式

    基於實體安全,USBGuard 預設拒絕所有未知裝置。你需要查詢該裝置的臨時 ID 並手動核發許可。

    1. 列出目前被阻擋的裝置(尋找標示為 block 的項目):

      sudo usbguard list-devices
      
    2. 記下該裝置最前方的數字 ID,並將其永久加入白名單:

      sudo usbguard allow-device 14 -p
      

5. 基準線更新與誤判排除

  • 情境:RKHunter 透過 Systemd Timer 排程掃描後,回報 /dev/shm 記憶體掛載點底下存在可疑檔案(通常是 PulseAudio 或是某些共用記憶體的合法行程所產生)。

  • 回應方式

    這是防毒軟體常見的誤判/誤報。你需要對 RKHunter 進行調校並更新系統指紋基準線:

    1. 編輯設定檔 sudo nano /etc/rkhunter.conf

    2. 尋找並加入允許的特定路徑,例如:ALLOWDEVFILE=/dev/shm/pulse-shm-*

    3. 儲存後,強制 RKHunter 重新採集並信任當前的系統狀態:

      sudo rkhunter --propupd
      

結語

安全不是單一的產品,而是一個持續的過程。透過理解作業系統的底層運作機制,我們能夠用合理的資源損耗,編織出覆蓋面廣泛的防禦網。希望這份文件能為你的基礎架構維運帶來啟發與實質的安全提升。